package tcp;

import java.net.Socket;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * This TCPServer maintains a connection with many clients. To receive
 * the incoming objects, register yourself as an observer.
 * 
 * TODO: 
 * @author Kristopher L. Kalish, James Madison University
 * @version 1 - February 23, 2008 
 * @version 2 - April 6, 2008 Added capability to send objects
 *
 */
public class ThreadedTCPServer implements Runnable, TCPObserver, NewConnectionObserver, TCPSubject
{
	public static final int LISTEN_PORT = 5000;
	
	private volatile boolean     isRunning;
	private ConnectionListener cl; // don't really need this reference
	
	private List<Object>         objects;
	private List<TCPObserver>    observers;
	private List<ThreadedSessionHandler> handlers;
	
	private Thread               controlThread;
	
	private Object lock = new Object();
	
	
	public ThreadedTCPServer() 
	{
		cl = new ConnectionListener(LISTEN_PORT);
		cl.start();
		cl.addNewConnectionObserver(this);
		
		observers =
			 Collections.synchronizedList(new LinkedList<TCPObserver>());
		
		objects =
			 Collections.synchronizedList(new LinkedList<Object>());
		
		handlers =
			 Collections.synchronizedList(new LinkedList<ThreadedSessionHandler>());
		
	}
	
	public void addObserver(TCPObserver tcpo)
	{
		observers.add(tcpo);
	}
	
	public void handleNewConnection(Socket s)
	{
		synchronized(lock)
		{
			ThreadedSessionHandler sh = new ThreadedSessionHandler(s, this, true);
			handlers.add(sh);
			sh.start();
		}
	}
	
	public void handleObject(Object o)
	{
		synchronized(lock)
		{
			//System.out.println("Handling object " + o + ".");
			objects.add(o);
			lock.notifyAll();
		}
	}
	
	public void notifyObservers(Object o)
	{
		for(Iterator<TCPObserver> i = observers.iterator(); i.hasNext(); ) 
			i.next().handleObject(o);
	}
	
	public void removeObserver(TCPObserver tcpo)
	{
		observers.remove(tcpo);
	}
	
	public void run()
	{
		while(isRunning)
		{
			synchronized(lock) 
			{
				System.out.println("Notifying observers...");
				for(Iterator<Object> i = objects.iterator(); i.hasNext(); ) 
					this.notifyObservers(i.next());
					
				try {
					lock.wait();
				} catch (InterruptedException ie) {
					// do nothing
				}
			}
		} 
	}
	
	/**
	 * Sends an object to everyone connected.
	 * Delegates to the SessionHandlers.
	 * @param obj Data to send.
	 */
	public void sendToAll(Object obj)
	{
		for(ThreadedSessionHandler handler : handlers)
			handler.send(obj);
	}
	
	public void start()
	{
		if(controlThread == null)
		{
			isRunning = true;
			controlThread = new Thread(this);
			controlThread.start();
		}
	}
}